package jsr292.java.lang.invoke;

import java.lang.reflect.Array;

import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;

class MHCollector extends MethodHandle {
    private final MethodHandle mh;
    private final int arrayLength;
    private final Class<?> componentType;
    
    public MHCollector(MethodHandle mh, MethodType newType, int arrayLength, Class<?> componentType) {
        super(newType);
        this.mh = mh;
        
        this.arrayLength = arrayLength;
        this.componentType =  componentType;
    }
    
    private Object[] createArray(int length) {
        return (Object[])Array.newInstance(componentType, length);
    }
    
    private Object convert(int index, Object value) {
        return Conversions.convert(MethodType.erase(methodType.parameterType(index)), componentType, value);
    }
    
    @Override
    public Object $invoke$() throws Throwable {
        return mh.$invoke$((Object)createArray(0));
    }
    
    @Override
    public Object $invoke$(Object o1) throws Throwable {
        int arrayLength = this.arrayLength;
        Object[] array = createArray(arrayLength);
        switch(arrayLength) {
        case 0:
            return mh.$invoke$(o1, array);
        case 1:
            array[0] = convert(0, o1);
            return mh.$invoke$((Object)array);
        }
        throw super.fail();
    }
    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
        int arrayLength = this.arrayLength;
        Object[] array = createArray(arrayLength);
        switch(arrayLength) {
        case 0:
            return mh.$invoke$(o1, o2, array);
        case 1:
            array[0] = convert(1, o2);
            return mh.$invoke$(o1, array);
        case 2:
            array[0] = convert(0, o1);
            array[1] = convert(1, o2);
            return mh.$invoke$((Object)array);
        }
        throw super.fail();
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
        int arrayLength = this.arrayLength;
        Object[] array = createArray(arrayLength);
        switch(arrayLength) {
        case 0:
            return  mh.$invoke$(o1, o2, o3, array);
        case 1:
            array[0] = convert(2, o3);
            return mh.$invoke$(o1, o2, array);
        case 2:
            array[0] = convert(1, o2);
            array[1] = convert(2, o3);
            return mh.$invoke$(o1, array);
        case 3:
            array[0] = convert(0, o1);
            array[1] = convert(1, o2);
            array[2] = convert(2, o3);
            return mh.$invoke$((Object)array);
        }
        throw super.fail();
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
        int arrayLength = this.arrayLength;
        Object[] array = createArray(arrayLength);
        switch(arrayLength) {
        case 0:
            return  mh.$invoke$(o1, o2, o3, o4, array);
        case 1:
            array[0] = convert(3, o4);
            return  mh.$invoke$(o1, o2, o3, array);
        case 2:
            array[0] = convert(2, o3);
            array[1] = convert(3, o4);
            return mh.$invoke$(o1, o2, array);
        case 3:
            array[0] = convert(1, o2);
            array[1] = convert(2, o3);
            array[2] = convert(3, o4);
            return mh.$invoke$(o1, array);
        case 4:
            array[0] = convert(0, o1);
            array[1] = convert(1, o2);
            array[2] = convert(2, o3);
            array[3] = convert(3, o4);
            return mh.$invoke$((Object)array);
        }
        throw super.fail();
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
        int arrayLength = this.arrayLength;
        Object[] array = createArray(arrayLength);
        switch(arrayLength) {
        case 0:
            return  mh.$invoke$(o1, o2, o3, o4, o5, array);
        case 1:
            array[0] = convert(4, o5);
            return  mh.$invoke$(o1, o2, o3, o4, array);
        case 2:
            array[0] = convert(3, o4);
            array[1] = convert(4, o5);
            return  mh.$invoke$(o1, o2, o3, array);
        case 3:
            array[0] = convert(2, o3);
            array[1] = convert(3, o4);
            array[2] = convert(4, o5);
            return mh.$invoke$(o1, o2, array);
        case 4:
            array[0] = convert(1, o2);
            array[1] = convert(2, o3);
            array[2] = convert(3, o4);
            array[3] = convert(4, o5);
            return mh.$invoke$(o1, array);
        case 5:
            array[0] = convert(0, o1);
            array[1] = convert(1, o2);
            array[2] = convert(2, o3);
            array[3] = convert(3, o4);
            array[4] = convert(4, o5);
            return mh.$invoke$((Object)array);
        }
        throw super.fail();
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
        int arrayLength = this.arrayLength;
        Object[] array = createArray(arrayLength);
        switch(arrayLength) {
        case 0:
            return  mh.$invoke$(o1, o2, o3, o4, o5, o6, array);
        case 1:
            array[0] = convert(5, o6);
            return  mh.$invoke$(o1, o2, o3, o4, o5, array);
        case 2:
            array[0] = convert(4, o5);
            array[1] = convert(5, o6);
            return  mh.$invoke$(o1, o2, o3, o4, array);
        case 3:
            array[0] = convert(3, o4);
            array[1] = convert(4, o5);
            array[2] = convert(5, o6);
            return  mh.$invoke$(o1, o2, o3, array);
        case 4:
            array[0] = convert(2, o3);
            array[1] = convert(3, o4);
            array[2] = convert(4, o5);
            array[3] = convert(5, o6);
            return mh.$invoke$(o1, o2, array);
        case 5:
            array[0] = convert(1, o2);
            array[1] = convert(2, o3);
            array[2] = convert(3, o4);
            array[3] = convert(4, o5);
            array[4] = convert(5, o6);
            return mh.$invoke$(o1, array);
        case 6:
            array[0] = convert(0, o1);
            array[1] = convert(1, o2);
            array[2] = convert(2, o3);
            array[3] = convert(3, o4);
            array[4] = convert(4, o5);
            array[5] = convert(5, o6);
            return mh.$invoke$((Object)array);
        }
        throw super.fail();
    }
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
        int arrayLength = this.arrayLength;
        Object[] array = createArray(arrayLength);
        switch(arrayLength) {
        case 0:
            return  mh.$invoke$(o1, o2, o3, o4, o5, o6, o7, array);
        case 1:
            array[0] = convert(6, o7);
            return  mh.$invoke$(o1, o2, o3, o4, o5, o6, array);
        case 2:
            array[0] = convert(5, o6);
            array[1] = convert(6, o7);
            return  mh.$invoke$(o1, o2, o3, o4, o5, array);
        case 3:
            array[0] = convert(4, o5);
            array[1] = convert(5, o6);
            array[2] = convert(6, o7);
            return  mh.$invoke$(o1, o2, o3, o4, array);
        case 4:
            array[0] = convert(3, o4);
            array[1] = convert(4, o5);
            array[2] = convert(5, o6);
            array[3] = convert(6, o7);
            return  mh.$invoke$(o1, o2, o3, array);
        case 5:
            array[0] = convert(2, o3);
            array[1] = convert(3, o4);
            array[2] = convert(4, o5);
            array[3] = convert(5, o6);
            array[4] = convert(6, o7);
            return mh.$invoke$(o1, o2, array);
        case 6:
            array[0] = convert(1, o2);
            array[1] = convert(2, o3);
            array[2] = convert(3, o4);
            array[3] = convert(4, o5);
            array[4] = convert(5, o6);
            array[5] = convert(6, o7);
            return mh.$invoke$(o1, array);
        case 7:
            array[0] = convert(0, o1);
            array[1] = convert(1, o2);
            array[2] = convert(2, o3);
            array[3] = convert(3, o4);
            array[4] = convert(4, o5);
            array[5] = convert(5, o6);
            array[6] = convert(6, o7);
            return mh.$invoke$((Object)array);
        }
        throw super.fail();
    }

    @Override
    public Object $invoke$(Object... rest) throws Throwable {
        int restLength = rest.length;
        int position = restLength - this.arrayLength + 1;
	Object[] array = ArrayUtils.copyOfRange(rest, position, restLength);
	rest = rest.clone();   //FIXME is it necessary ?
	rest[position] = array;
        return mh.$invoke$(rest);
    }
    
    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        return mh.isWeavable(classBeingRedefined);
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        
	OptimizerSnippets.collect(context, allocator, arrayLength, componentType, methodType, mh);
    }
}
